# 前端路由
在 vue 中,我们就知道了前端路由的核心是:改变 URL 时,页面不进行整体的刷新。
并且常见的路由实现方式有:
- URL 的 hash
- HMTL5 的 history
# 一、react-router
# 1、安装配置
目前 react-router (opens new window) 已经更新到了 v6.x 版本,相较于之前的版本发生了较大的变化
本处的笔记,以 v6.x 版本为主,以对比其与 v4.x/v5.x 版本差异为辅。
react-router 官方主要提供了下面几个 Package 包供我们使用:
- react-router
- react-router-dom
- react-router-native
安装:
npm install react-router-dom
配置:
// index.js
import { BrowserRouter, HashRouter } from 'react-router-dom'
// ……
root.render(
<HashRouter>
<App />
</HashRouter>
)
# 2、基础示例
react-router 中的<Link>
、<Route>
类似于 vue-router 中的<router-link>
、<router-view>
router 配置
在 react-router 中,router 规则是直接放在 App.jsx 中的
<div className="content">
{
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/cart" element={<Cart />} />
<Route path="/search" element={<Search />} />
</Routes>
}
</div>
而在 vue-router 中,这些 router 规则是需要抽离出来进行单独管理的
<div class="content">
<router-view></router-view>
</div>
版本差异
在 v5.x 版本中,Route 最外层包裹的是 <Switch>
组件而不是 <Routes>
组件
在 v5.x 版本中,使用的是 component 属性(和 vue-router 保持一致),而不是 element 属性
import { Route, Routes, Link } from 'react-router-dom'
class App extends React.PureComponent {
render() {
return (
<div className="app">
{/* …… */}
<div className="nav-content">
<div className="nav">
<Link to="/home">首页</Link>
<Link to="/cart">购物车</Link>
<Link to="/search">搜索</Link>
</div>
<div className="content">
{/* 映射关系:path => Component */}
{
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/cart" element={<Cart />} />
<Route path="/search" element={<Search />} />
</Routes>
}
</div>
</div>
{/* …… */}
</div>
)
}
}
拓展:replace 属性 与 NavLink 组件
下面其实是 react-router 中一些和 vue-router 相似的功能
- 去除历史记录
replace 属性:防止产生历史记录
// vue-router 中
<router-link replace to="/home">首页</router-link>
// react-router 中
<Link replace={true} to="/home">
首页
</Link>
- nav 选中样式
核心就是动态添加一个'active'的 class 类名
// vue-router 中
// 详见:https://v3.router.vuejs.org/zh/api/#active-class
<router-link active-class="active" to="/home">首页</router-link>
// react-router 中
// 详见:https://reactrouter.com/en/main/components/nav-link
<NavLink to="/home" >首页</NavLink>
# 3、重定向
直接使用<Navigate>
(opens new window)组件即可
版本差异
在 v5.x 版本中,使用的是 <Redirect>
(opens new window),而不是这里的<Navigate>
- 场景 1
<div>
<h2>Login Page</h2>
{isLogin ? <Navigate to="/home" /> : <button>登录</button>}
</div>
- 场景 2
<div className="content">
{
<Routes>
{/* 重定向1 ✖ */}
{/* <Route path="/" element={<Home />} /> */}
{/* 重定向2 ✔ */}
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<Home />} />
<Route path="/cart" element={<Cart />} />
<Route path="/search" element={<Search />} />
</Routes>
}
</div>
拓展:NotFound 页面
<Route path="*" element={<NotFound />} />
# 二、Route 相关
前面我们对 Route 组件的使用仅限于 path 属性和 element 属性,其实 Route 组件还有很多其他的用途:
Route 事件的所有可用 props
interface RouteObject {
path?: string
index?: boolean
children?: React.ReactNode
caseSensitive?: boolean
id?: string
loader?: LoaderFunction
action?: ActionFunction
element?: React.ReactNode | null
Component?: React.ComponentType | null
errorElement?: React.ReactNode | null
ErrorBoundary?: React.ComponentType | null
handle?: RouteObject['handle']
shouldRevalidate?: ShouldRevalidateFunction
lazy?: LazyRouteFunction<RouteObject>
}
# 1、路由嵌套
官方文档中将其叫做:layout route。就是将子路由直接嵌套在<Route></Route>
内。
v6.x 中使用 <Route></Route>
+ <Outlet>
这种写法的,可以防止 Route 过于分散
版本差异
在 v5.x 中是没有<Outlet>
的,所以发生路由嵌套时,正常写就可以了
// App.jsx
<div className="app">
{/* …… */}
<div className="nav-content">
<div className="nav">
<Link to="/home">首页</Link>
<Link to="/cart">购物车</Link>
<Link to="/search">搜索</Link>
</div>
<div className="content">
{
<Routes>
<Route path="/" element={<Navigate to="/home" />} /> {/* 重定向 */}
{/* 若存在子路由 */}
<Route path="/home" element={<Home />}>
<Route path="/home" element={<Navigate to="/home/part1" />} /> {/* 重定向 */}
<Route path="/home/part1" element={<Part1 />} />
<Route path="/home/part2" element={<Part2 />} />
</Route>
<Route path="/cart" element={<Cart />} />
<Route path="/search" element={<Search />} />
</Routes>
}
</div>
</div>
{/* …… */}
</div>
- home 组件
<div>
{/* …… */}
<div className="nav-content">
<div className="nav">
<Link to="/home/part1">推荐</Link>
<Link to="/home/part2">排行榜</Link>
</div>
<div className="content">
{/* 占位 */}
<OutLet />
</div>
</div>
{/* …… */}
</div>
# 2、router 配置
在 react-router@6 中,我们可以直接使用useRoutes(routes)
将 route 提取到单独的文件中进行配置:
版本差异
在 react-router@5 中,同样可以可对 router 规则进行单独配置,只不过需要借助 react-router-config (opens new window) 库
- App.jsx
import { useRoutes } from 'react-router-dom'
import routes from '@/router/index.js'
class App extends React.PureComponent {
render() {
return (
<div className="app">
{/* …… */}
<div className="nav-content">
<div className="nav">
<Link to="/home">首页</Link>
<Link to="/cart">购物车</Link>
<Link to="/search">搜索</Link>
</div>
<div className="content">
{/* 映射关系:path => Component */}
{useRoutes(routes)}
{/* {
<Routes>
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<Home />}>
<Route path="/home" element={<Navigate to="/home/part1" />} />
<Route path="/home/part1" element={<Part1 />} />
<Route path="/home/part2" element={<Part2 />} />
</Route>
<Route path="/cart" element={<Cart />} />
<Route path="/search" element={<Search />} />
</Routes>
} */}
</div>
</div>
{/* …… */}
</div>
)
}
}
- @/router/index.js
const routes = [
{ path: '/', element: <Navigate to="/home" /> },
{
path: '/home',
element: <Home />,
children: [
{ path: '/home', element: <Navigate to="/home/part1" /> },
{ path: '/home/part1', element: <Part1 /> },
{ path: '/home/part2', element: <Part2 /> }
]
},
{ path: '/cart', element: <Cart /> },
{ path: '/search', element: <Search /> }
]
export default routes
# 3、懒加载
和 vue-react 应用,react-router 同样可以通过懒加载实现分包处理:
// 以前
import Home from '../pages/Home.jsx'
// 现在
const Home = React.lazy(() => import('../pages/Home.jsx'))
这样的话,开发中我们可以这样使用:
// 根index.js
root.render(
<Suspense fallback={<div>Loading</div>}>
<HashRouter>
<App />
</HashRouter>
</Suspense>
)
// @/router/index.js
const routes = [
{ path: '/cart', element: React.lazy(() => import('../pages/Cart.jsx')) }
//
]